/**
 * Classe permettant de faire des DOMExp, des expressions permettant de parser un élément DOM
 * 
 * Il s'agit d'une alternative à la RegExp, qui, dans le contexte HTML, possède des limites pour déterminer le début et fin de balise
 * 
 * La classe est écrite comme la classe RegExp:
 * var r = new DOMExp("expression")
 * var matches = r.exec(dom); // dom est un élément obtenu via document.getElementById ou un objet Document
 * if (!r.test(dom))
 * 		; // L'élément n'a pas pu etre trouvé
 * 
 * ** Ecriture des DOMExp
 * 
 * Une expression DOMExp est constitué de plusieurs éléments séparés chacun par des espaces:
 * [element1] [element2] [...]
 * 
 * Chaque élément correspond à une opération précise sur le DOM, sur un nom donné.
 * span: sélectionne les éléments ayant le tag HTML span
 * .classname: sélectionne les éléments HTML ayant la classe CSS classname
 * #idelement: sélectionne l'élément d'ID idelement
 * 
 * Pour les recherches multiples par tag ou classes, il est possible de renseigner un index, entre crochets (comme un tableau):
 * span[0]: sélectionne le premier span
 * .classname[2]: sélectionne le 3ème élément ayant la classe classname
 * 
 * Par défaut, si une sélection par tag ou classe est faite avant la fin de l'expression, c'est le 1er élément qui est pris en compte
 * Il est donc en générale inutile de préciser span[0] ou .classname[0], et juste indiquer span ou .classname
 * 
 * Pour le cas des classes, s'il faut sélectionner un ensemble de classe, il faut les séparer par des "+" au lieu de " ":
 * .class1+.class2 (correspond à document.getElementsByClassName("class1 class2"))
 * 
 * Exemple d'expression:
 * 
 * .class #div_num_1 span[2]
 * 
 * @param {String} expression l'expression DOMExp
 */
var DOMExp = function(expression)
{
	/**
	 * L'expression DOM
	 * @type {String}
	 */
	var _expression;
	
	/**
	 * Parse l'expression pour obtenir un tableau des éléments à rechercher
	 * Remplace automatiquement les "+" par des " "
	 * 
	 * @return {Array} le tableau des éléments à rechercher
	 */
	function _parseExpression()
	{
		// On split par le séparateur
		var list = _expression.split(" ")
		
		for (var i = 0; i < list.length; i++)
		{
			// Si ya des +, on les remplace par des " "
			if (list[i].indexOf("+") != -1)
				list[i] = list[i].replace("+"," ");
		}
		
		return list;
	};
	
	/**
	 * document.getElementById pour un élément du DOM
	 * 
	 * Renvoie l'élément d'id "id" contenu dans un élément "dom"
	 * 
	 * @param {HTMLElement|Document} dom un élément HTML ou un Document
	 * @param {String} id l'id à rechercher
	 */
	function _getElementById(dom, id)
	{
		// Si getElementById existe, on l'utilise à la place
		if (typeof(dom.getElementById) == "function")
			return dom.getElementById(id);
		
		// On parcourt une première fois les noeuds pour voir si l'id est un enfant direct
		for (var i = 0; i < dom.childNodes.length; i++)
		{
			var node = dom.childNodes[i]
			// nodeType de ElementNode = 1
			if (node.nodeType != 1)
				continue;
			
			// Si on trouve l'id, on s'arrête
			if (node.hasAttribute("id") && node.getAttribute("id") == id)
				return node;
		}
		
		// Parcours une deuxième fois des enfants si l'id n'a pas été trouvé
		for (var i = 0; i < dom.childNodes.length; i++)
		{
			var node = dom.childNodes[i]
			// nodeType de ElementNode = 1
			if (node.nodeType != 1)
				continue;
			
			
			// Récursivité
			var returnNode = _getElementById(node,id)
			// Si on l'a trouvé, on le renvoie
			if (returnNode != null)
				return returnNode;
		}
		
		return null;
	};	
	
	/**
	 * Parse l'élément DOM avec l'expression enregistré
	 * 
	 * @param {HTMLElement|Document} dom un élément HTML ou un Document
	 * @param {int} type_result le type de résultat: DOMExp.ONE_RESULT, DOMExp.MULTIPLE_RESULT ou DOMExp.BOOL_RESULT
	 */
	function _parseDOM(dom, type_result)
	{
		// Liste des éléments à rechercher
		var list = _parseExpression()
		
		var elem = dom;
		
		// S'il y a interruption de la boucle car non trouvé
		var breakFor = false;
		// S'il y a multiplicité du résultat de fin
		var isMultiple = false;
		
		
		for (var i = 0; i < list.length; i++)
		{
			var l = list[i]
			// Premier caractère
			var firstc = l[0]
			// Index de tableau (0 par défaut)
			var index = 0
			// S'il y a un index de précisé
			var haveIndex = false;
			
			// S'il s'agit du dernier élément
			var isLast = (i == list.length-1)
			
			name = l;
			
			// S'il n'y a rien, ou qu'il y a un ., recherche de l'existence d'un index
			if ((firstc != "#" && firstc != ".") || firstc == ".")
			{
				// Expression pour les index nom[index]
				var regexp_index = /(.*)(\[[0-9]*\])/
				if (regexp_index.test(l))
				{
					var matches = regexp_index.exec(l)
					index = parseInt(matches[2][1])
					name = matches[1]
					haveIndex = true;
				}
			}
			
			// S'il y a un premier caractère, on le supprime du nom
			if (firstc == "#" || firstc == ".")
				name = name.substr(1);
			
			switch(firstc)
			{
				// Classe CSS
				case ".":
					var d = elem.getElementsByClassName(name)
					// Dernier élément trouvé + pas d'index précisé
					if (d && isLast && !haveIndex)
					{
						elem = d;
						isMultiple = true;
					}
					// Element trouvé
					else if (d && d.length > index)
						elem = d[index];
					// Element non trouvé
					else
					{
						breakFor = true;
						elem = null;
					}
					break;
				
				// ID
				case "#":
					var d = _getElementById(elem,name)
					// Element non trouvé
					if (!d)
					{
						breakFor = true;
						elem = null;						
					}
					// Element trouvé
					else
						elem = d;
						
					break;
				
				// Par Tag
				default:
					var d = elem.getElementsByTagName(name)
					// Dernier élément trouvé + pas d'index précisé
					if (d && isLast && !haveIndex)
					{
						elem = d;
						isMultiple = true;
					}
					// Element trouvé
					else if (d && d.length > index)
						elem = d[index]
					// Element non trouvé
					else
					{
						breakFor = true;
						elem = null;
					}
					break;
					
			}
			
			// Si l'élément n'est pas trouvé, on arrête la boucle
			if (breakFor)
				break;
		}
		var result = elem;
		
		
		
		switch(type_result)
		{
			case DOMExp.ONE_RESULT:
				// Cas ou résultats multiples sans index précisé
				if (isMultiple && !haveIndex && elem != null)
					result = elem[0]
				break;
			case DOMExp.MULTIPLE_RESULT:
				// Cas d'un seul résultat trouvé
				if (!isMultiple && elem != null)
					result = [elem]
				// Aucun élément trouvé
				else if (elem == null)
					result = []
				break;
				
			case DOMExp.BOOL_RESULT:
				// Aucun élément trouvé
				if (elem == null)
					result = false;
				// Element trouvé
				else
					result = true;
				break;
		}
		
		
		return result;
	};
	
	/**
	 * Exécute l'expression sur l'élément DOM voulu
	 * Le retour peut être soit un tableau, soit un seul élément.
	 * Pour que la fonction retourne un tableau, précisez en deuxième argument DOMExp.MULTIPLE_RESULT
	 * 
	 * @param {HTMLElement|Document} dom un élément HTML ou un Document
	 * @param {int} type_result le type de résultat: DOMExp.ONE_RESULT ou DOMExp.MULTIPLE_RESULT. Défaut: DOMExp.ONE_RESULT
	 * 
	 * @return {HTMLElement|Array} l'élément trouvé (DOMExp.ONE_RESULT), les éléments trouvés (DOMExp.MULTIPLE_RESULT), null si non trouvé (DOMExp.ONE_RESULT), tableau vide si non trouvés (DOMExp.MULTIPLE_RESULT)
	 */
	this.exec = function(dom, type_result)
	{
		if (typeof(type_result) == "undefined")
			type_result = DOMExp.ONE_RESULT;
		
		return _parseDOM(dom,type_result)
	};
	
	/**
	 * Test l'expression sur l'élément DOM voulu
	 * @param {HTMLElement|Document} dom un élément HTML ou un Document
	 * 
	 * @return {bool} True si un élément est trouvé, false sinon
	 */
	this.test = function(dom)
	{
		return _parseDOM(dom, DOMExp.BOOL_RESULT);
	};
	
	/**
	 * Constructeur de la classe
	 * 
	 * @param {String} expression l'expression
	 */
	function _construct(expression)
	{
		_expression = expression;
	}
	
	_construct(expression);
};

/**
 * Un seul élément sera renvoyé avec exec
 */
DOMExp.ONE_RESULT = 1;
/**
 * Plusieurs éléments sont renvoyés avec exec (si terminant par une classe ou un tag)
 */
DOMExp.MULTIPLE_RESULT = 2;
/**
 * True si un élément est trouvé, false sinon
 */
DOMExp.BOOL_RESULT = 3;
